/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// package com.google.zxing.pdf417;
// import com.google.zxing.BarcodeFormat;
// import com.google.zxing.EncodeHintType;
// import com.google.zxing.Writer;
// import com.google.zxing.WriterException;
// import com.google.zxing.common.BitMatrix;
// import com.google.zxing.pdf417.encoder.Compaction;
// import com.google.zxing.pdf417.encoder.Dimensions;
// import com.google.zxing.pdf417.encoder.PDF417;
//
// import java.nio.charset.Charset;
// import java.util.Map;

/**
 * @author Jacob Haynes
 * @author qwandor@google.com (Andrew Walbran)
 */
import Writer from '../Writer';
import {int} from '../../customTypings';
import EncodeHintType from '../EncodeHintType';
import BitMatrix from '../common/BitMatrix';
import IllegalArgumentException from '../IllegalArgumentException';
import {PDF417} from './encoder/PDF417';
import BarcodeFormat from '../BarcodeFormat';
import {Compaction} from './encoder/Compaction';
import {Dimensions} from './encoder/Dimensions';
import Integer from '../util/Integer';
import Charset from '../util/Charset';

/*public final*/
export default class PDF417Writer implements Writer {

  /**
   * default white space (margin) around the code
   */
  private static WHITE_SPACE: int = 30;

  /**
   * default error correction level
   */
  private static DEFAULT_ERROR_CORRECTION_LEVEL: int = 2;

  public encode(contents: string,
                format: BarcodeFormat,
                width: int,
                height: int,
                hints: Map<EncodeHintType, any>): BitMatrix {
    if (format !== BarcodeFormat.PDF_417) {
      throw new IllegalArgumentException('Can only encode PDF_417, but got ' + format);
    }

    let encoder: PDF417 = new PDF417(false);
    let margin: int = PDF417Writer.WHITE_SPACE;
    let errorCorrectionLevel: int = PDF417Writer.DEFAULT_ERROR_CORRECTION_LEVEL;

    if (hints != null) {
      if (hints.has(EncodeHintType.PDF417_COMPACT)) {
        encoder.setCompact(hints.get(EncodeHintType.PDF417_COMPACT).toString() !== undefined && hints.get(EncodeHintType.PDF417_COMPACT) === 'true');
      }
      if (hints.has(EncodeHintType.PDF417_COMPACTION)) {
        encoder.setCompaction(PDF417Writer.valueOf(hints.get(EncodeHintType.PDF417_COMPACTION).toString()));
      }
      if (hints.has(EncodeHintType.PDF417_DIMENSIONS)) {
        let dimensions: Dimensions = hints.get(EncodeHintType.PDF417_DIMENSIONS);
        encoder.setDimensions(dimensions.getMaxCols(),
          dimensions.getMinCols(),
          dimensions.getMaxRows(),
          dimensions.getMinRows());
      }
      if (hints.has(EncodeHintType.MARGIN)) {
        margin = Integer.parse(hints.get(EncodeHintType.MARGIN).toString());
      }
      if (hints.has(EncodeHintType.ERROR_CORRECTION)) {
        errorCorrectionLevel = Integer.parse(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
      }
      if (hints.has(EncodeHintType.CHARACTER_SET)) {
        let encoding: Charset = Charset.forName(hints.get(EncodeHintType.CHARACTER_SET).toString());
        encoder.setEncoding(encoding);
      }
    }

    return PDF417Writer.bitMatrixFromEncoder(encoder, contents, errorCorrectionLevel, width, height, margin);
  }

  /*@Override*/
  // public encode(contents: string,
  //                           format: BarcodeFormat,
  //                           width: int,
  //                           height: int): BitMatrix {
  //   return this.encode(contents, format, width, height, null);
  // }

  private static valueOf(value: string) {
    switch (parseInt(value)) {
      case 0:
        return Compaction.AUTO;
      case 1:
        return Compaction.TEXT;
      case 2:
        return Compaction.BYTE;
      case 3:
        return Compaction.NUMERIC;
    }
  }

  /**
   * Takes encoder, accounts for width/height, and retrieves bit matrix
   */
  private static bitMatrixFromEncoder(encoder: PDF417,
                                      contents: string,
                                      errorCorrectionLevel: int,
                                      width: int,
                                      height: int,
                                      margin: int): BitMatrix {
    encoder.generateBarcodeLogic(contents, errorCorrectionLevel);

    let aspectRatio: int = 4;
    let originalScale: Uint8Array[] = encoder.getBarcodeMatrix().getScaledMatrix(1, aspectRatio);
    let rotated: boolean = false;
    if ((height > width) !== (originalScale[0].length < originalScale.length)) {
      originalScale = PDF417Writer.rotateArray(originalScale);
      rotated = true;
    }

    let scaleX: int = Math.floor(width / originalScale[0].length);
    let scaleY: int = Math.floor(height / originalScale.length);
    let scale: int = Math.min(scaleX, scaleY);

    if (scale > 1) {
      let scaledMatrix: Uint8Array[] =
        encoder.getBarcodeMatrix().getScaledMatrix(scale, scale * aspectRatio);
      if (rotated) {
        scaledMatrix = PDF417Writer.rotateArray(scaledMatrix);
      }
      return PDF417Writer.bitMatrixFromBitArray(scaledMatrix, margin);
    }
    return PDF417Writer.bitMatrixFromBitArray(originalScale, margin);
  }

  /**
   * This takes an array holding the values of the PDF 417
   *
   * @param input a byte array of information with 0 is black, and 1 is white
   * @param margin border around the barcode
   * @return BitMatrix of the input
   */
  private static bitMatrixFromBitArray(input: Uint8Array[], margin: int): BitMatrix {
    // Creates the bit matrix with extra space for whitespace
    let output: BitMatrix = new BitMatrix(input[0].length + 2 * margin, input.length + 2 * margin);
    output.clear();
    for (let y: int = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) {
      let inputY: Uint8Array = input[y];
      for (let x: int = 0; x < input[0].length; x++) {
        // Zero is white in the byte matrix
        if (inputY[x] === 1) {
          output.set(x + margin, yOutput);
        }
      }
    }
    return output;
  }

  /**
   * Takes and rotates the it 90 degrees
   */
  private static rotateArray(bitarray: Uint8Array[]): Uint8Array[] {
    let temp: Uint8Array[] = new Array<Uint8Array>();
    for (let ii: int = 0; ii < bitarray.length; ii++) {
      // This makes the direction consistent on screen when rotating the
      // screen;
      let inverseii: int = bitarray.length - ii - 1;
      for (let jj: int = 0; jj < bitarray[0].length; jj++) {
        temp[jj][inverseii] = bitarray[ii][jj];
      }
    }
    return temp;
  }
}
